PIC Tutorial Five - Infrared CommunicationTo complete all of these tutorials you will require two Main Boards, two IR Boards, the LCD Board, the Switch Board, and the LED Board, as written the first two tutorials use the LCD Board and Switch Board on PortA and the IR Boards on PortB - although these could easily be swapped over, as the IR Board doesn't use either of the two 'difficult' pins for PortA, pins 4 and 5. The third tutorial uses the IR Board on PortA and the LED Board on PortB (as we require all 8 pins to be outputs). Download zipped tutorial files. IR transmission has limitations, the most important one (for our purposes) being that the receiver doesn't give out the same width pulses that we transmit, so we can't just use a normal, RS232 type, serial data stream, where we simply sample the data at fixed times - the length of the received data varies with the number of ones sent - making receiving it accurately very difficult. Various different schemes are used by the manufacturers of IR remote controls, and some are much more complicated than others. I've chosen to use the Sony SIRC (Sony Infra Red Control) remote control system, many of you may already have a suitable Sony remote at home you can use, and it's reasonably easy to understand and implement. Basically it uses a pulse width system, with a start bit of 2.4mS, followed by 12 data bits, where a '1' is 1.2mS wide, and a '0' is 0.6mS wide, the bits are all separated by gaps of 0.6mS. The data itself consists of a 7 bit 'command' code, and a 5 bit 'device' code - where a command is Channel 1, Volume Up etc. and a device is TV, VCR etc. This is how the same remote system can be used for different appliances, the same command for 'Power On' is usually used by all devices, but by transmitting a device ID only a TV will respond to 'TV Power On' command.
The table to the right shows the data format, after the Start bit the command code is send, lowest bit first, then the device code, again lowest bit first. The entire series is sent repeatedly while the button is held down, every 45mS. In order to decode the transmissions we need to measure the width of the pulses, first looking for the long 'start' pulse, then measuring the next 12 pulses and deciding if they are 1's or 0's. To do this I'm using a simple software 8 bit counter, with NOP's in the loop to make sure we don't overflow the counter. After measuring one pulse we then test it to see if it's a valid pulse, this routine provides four possible responses 'Start Pulse', 'One', 'Zero', or 'Error', we initially loop until we get a 'Start Pulse' reply, then read the next 12 bits - if the reply to any of these 12 is other than 'One' or 'Zero' we abort the read and go back to waiting for a 'Start Pulse'.
The device codes used specify the particular device, but with a few exceptions!, while a TV uses device code 1, some of the Teletext buttons use code 3, as do the Fastext coloured keys - where a separate Widescreen button is fitted, this uses code 4. The table to the left shows some of the Device ID codes I found on a sample of Sony remotes. Five bits gives a possible 32 different device ID's, and some devices respond to more than one device ID, for example some of the current Sony VCR's have the Play button in a 'cursor' type of design, surrounded by 'Stop', 'Pause', 'Rewind', and 'Fast Forward' - the ones I tested actually send a DVD ID code when these keys are pressed (along with a different command ID to that used normally used for 'Play' etc.). However, they still respond to an older Sony remote which sends the VTR3 device ID, which despite being labelled VTR3 on TV remotes seems to be the normal standard Sony VCR device ID. It's quite common for Sony remotes to use more than one device ID, a Surround Sound Amplifier Remote I tried used four different device ID's. If you don't have a Sony remote you can use, I've also built a transmitter, using the second Main Board, second IR Board, and the Switch Board, the four buttons allow you to send four different command codes - I've chosen TV as the device, and Volume Up, Volume Down, Program Up, and Program Down as my four commands, I've confirmed this works on various Sony TV's. Transmitting the SIRC code is quite simple to do, I generate the 38KHz modulation directly in software, and to reduce current consumption don't use a 50/50 on/off ratio - by using a longer off than on time we still get the 38KHz, but with a reduced power requirement.
Tutorial 5.1 - requires one Main Board (with LED set to RB7), one IR Board and LCD Board. This program uses the LCD module to give a decimal display of the values of the Device and Command bytes transmitted by a Sony SIRC remote control, it can be easily altered to operate port pins to control external devices, as an example the main board LED is turned on by pressing button 2, turned off by pressing button 3, and toggled on and off by pressing button 1 (all on a TV remote, you can change the device ID for a different remote if you need to). As it stands it's very useful for displaying the data transmitted by each button on your Sony remote control - the Device ID's table above was obtained using this design. ;Tutorial 5_1 ;Read SIRC IR with LCD display ;Nigel Goodwin 2002 LIST p=16F628 ;tell assembler what chip we are using include "P16F628.inc" ;include the defaults for the chip ERRORLEVEL 0, -302 ;suppress bank selection messages __config 0x3D18 ;sets the configuration settings (oscillator type etc.) cblock 0x20 ;start of general purpose registers count ;used in looping routines count1 ;used in delay routine counta ;used in delay routine countb ;used in delay routine LoX Bit_Cntr Cmd_Byte Dev_Byte Timer_H Flags Flags2 tmp1 ;temporary storage tmp2 tmp3 lastdev lastkey NumL ;Binary inputs for decimal convert routine NumH TenK ;Decimal outputs from convert routine Thou Hund Tens Ones templcd ;temp store for 4 bit mode templcd2 endc LCD_PORT Equ PORTA LCD_TRIS Equ TRISA LCD_RS Equ 0x04 ;LCD handshake lines LCD_RW Equ 0x06 LCD_E Equ 0x07 IR_PORT Equ PORTB IR_TRIS Equ TRISB IR_In Equ 0x02 ;input assignment for IR data OUT_PORT Equ PORTB LED Equ 0x07 ErrFlag Equ 0x00 StartFlag Equ 0x01 ;flags used for received bit One Equ 0x02 Zero Equ 0x03 New Equ 0x07 ;flag used to show key released TV_ID Equ 0x01 ;TV device ID But1 Equ 0x00 ;numeric button ID's But2 Equ 0x01 But3 Equ 0x02 But4 Equ 0x03 But5 Equ 0x04 But6 Equ 0x05 But7 Equ 0x06 But8 Equ 0x07 But9 Equ 0x08 org 0x0000 goto Start org 0x0004 retfie ;TABLES - moved to start of page to avoid paging problems, ;a table must not cross a 256 byte boundary. HEX_Table addwf PCL , f retlw 0x30 retlw 0x31 retlw 0x32 retlw 0x33 retlw 0x34 retlw 0x35 retlw 0x36 retlw 0x37 retlw 0x38 retlw 0x39 retlw 0x41 retlw 0x42 retlw 0x43 retlw 0x44 retlw 0x45 retlw 0x46 Xtext addwf PCL, f retlw 'D' retlw 'e' retlw 'v' retlw 'i' retlw 'c' retlw 'e' retlw ' ' retlw ' ' retlw ' ' retlw 'C' retlw 'o' retlw 'm' retlw 'm' retlw 'a' retlw 'n' retlw 'd' retlw 0x00 ;end of tables Start movlw 0x07 movwf CMCON ;turn comparators off (make it like a 16F84) Initialise clrf count clrf PORTA clrf PORTB clrf Flags clrf Dev_Byte clrf Cmd_Byte SetPorts bsf STATUS, RP0 ;select bank 1 movlw 0x00 ;make all LCD pins outputs movwf LCD_TRIS movlw b'01111111' ;make all IR port pins inputs (except RB7) movwf IR_TRIS bcf STATUS, RP0 ;select bank 0 call LCD_Init ;setup LCD module call Delay255 ;let IR receiver settle down Main call LCD_Line1 ;set to first line call String1 ;display IR title string call ReadIR ;read IR signal movlw d'2' call LCD_Line2W ;set cursor position clrf NumH movf Dev_Byte, w ;convert device byte movwf NumL call Convert movf Tens, w call LCD_CharD movf Ones, w call LCD_CharD movlw d'11' call LCD_Line2W ;set cursor position clrf NumH movf Cmd_Byte, w ;convert data byte movwf NumL call Convert movf Hund, w call LCD_CharD movf Tens, w call LCD_CharD movf Ones, w call LCD_CharD call ProcKeys ;do something with commands received goto Main ;loop for ever ProcKeys btfss Flags2, New retlw 0x00 ;return if not new keypress movlw TV_ID ;check for TV ID code subwf Dev_Byte, w btfss STATUS , Z retlw 0x00 ;return if not correct code movlw But1 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z goto Key1 ;try next key if not correct code movf OUT_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED ;and test LED bit for toggling bsf OUT_PORT, LED ;turn on LED btfsc tmp3, LED bcf OUT_PORT, LED ;turn off LED bcf Flags2, New ;and cancel new flag retlw 0x00 Key1 movlw But2 ;test for button 2 subwf Cmd_Byte, w btfss STATUS , Z goto Key2 ;try next key if not correct code ;this time just turn it on bsf OUT_PORT, LED ;turn on LED bcf Flags2, New ;and cancel new flag retlw 0x00 Key2 movlw But3 ;test for button 3 subwf Cmd_Byte, w btfss STATUS , Z retlw 0x00 ;return if not correct code ;this time just turn it off bcf OUT_PORT, LED ;turn off LED bcf Flags2, New ;and cancel new flag retlw 0x00 String1 clrf count ;set counter register to zero Mess1 movf count, w ;put counter value in W call Xtext ;get a character from the text table xorlw 0x00 ;is it a zero? btfsc STATUS, Z retlw 0x00 ;return when finished call LCD_Char incf count, f goto Mess1 ;IR routines ReadIR call Read_Pulse btfss Flags, StartFlag goto ReadIR ;wait for start pulse (2.4mS) Get_Data movlw 0x07 ;set up to read 7 bits movwf Bit_Cntr clrf Cmd_Byte Next_RcvBit2 call Read_Pulse btfsc Flags, StartFlag ;abort if another Start bit goto ReadIR btfsc Flags, ErrFlag ;abort if error goto ReadIR bcf STATUS , C btfss Flags, Zero bsf STATUS , C rrf Cmd_Byte , f decfsz Bit_Cntr , f goto Next_RcvBit2 rrf Cmd_Byte , f ;correct bit alignment for 7 bits Get_Cmd movlw 0x05 ;set up to read 5 bits movwf Bit_Cntr clrf Dev_Byte Next_RcvBit call Read_Pulse btfsc Flags, StartFlag ;abort if another Start bit goto ReadIR btfsc Flags, ErrFlag ;abort if error goto ReadIR bcf STATUS , C btfss Flags, Zero bsf STATUS , C rrf Dev_Byte , f decfsz Bit_Cntr , f goto Next_RcvBit rrf Dev_Byte , f ;correct bit alignment for 5 bits rrf Dev_Byte , f rrf Dev_Byte , f retlw 0x00 ;end of ReadIR ;read pulse width, return flag for StartFlag, One, Zero, or ErrFlag ;output from IR receiver is normally high, and goes low when signal received Read_Pulse clrf LoX btfss IR_PORT, IR_In ;wait until high goto $-1 clrf tmp1 movlw 0xC0 ;delay to decide new keypress movwf tmp2 ;for keys that need to toggle Still_High btfss IR_PORT, IR_In ;and wait until goes low goto Next incfsz tmp1,f goto Still_High incfsz tmp2,f goto Still_High bsf Flags2, New ;set New flag if no button pressed goto Still_High Next nop nop nop nop nop ;waste time to scale pulse nop ;width to 8 bits nop nop nop nop nop nop incf LoX, f btfss IR_PORT, IR_In goto Next ;loop until input high again ; test if Zero, One, or Start (or error) Chk_Pulse clrf Flags TryError movf LoX, w ; check if pulse too small addlw d'255' - d'20' ; if LoX <= 20 btfsc STATUS , C goto TryZero bsf Flags, ErrFlag ; Error found, set flag retlw 0x00 TryZero movf LoX, w ; check if zero addlw d'255' - d'60' ; if LoX <= 60 btfsc STATUS , C goto TryOne bsf Flags, Zero ; Zero found, set flag retlw 0x00 TryOne movf LoX, w ; check if one addlw d'255' - d'112' ; if LoX <= 112 btfsc STATUS , C goto TryStart bsf Flags, One ; One found, set flag retlw 0x00 TryStart movf LoX, w ; check if start addlw d'255' - d'180' ; if LoX <= 180 btfsc STATUS , C goto NoMatch bsf Flags, StartFlag ; Start pulse found retlw 0x00 NoMatch ; pulse too long bsf Flags, ErrFlag ; Error found, set flag retlw 0x00 ;end of pulse measuring routines ;LCD routines ;Initialise LCD LCD_Init call LCD_Busy ;wait for LCD to settle movlw 0x20 ;Set 4 bit mode call LCD_Cmd movlw 0x28 ;Set display shift call LCD_Cmd movlw 0x06 ;Set display character mode call LCD_Cmd movlw 0x0c ;Set display on/off and cursor command call LCD_Cmd ;Set cursor off call LCD_Clr ;clear display retlw 0x00 ; command set routine LCD_Cmd movwf templcd swapf templcd, w ;send upper nibble andlw 0x0f ;clear upper 4 bits of W movwf LCD_PORT bcf LCD_PORT, LCD_RS ;RS line to 1 call Pulse_e ;Pulse the E line high movf templcd, w ;send lower nibble andlw 0x0f ;clear upper 4 bits of W movwf LCD_PORT bcf LCD_PORT, LCD_RS ;RS line to 1 call Pulse_e ;Pulse the E line high call LCD_Busy retlw 0x00 LCD_CharD addlw 0x30 ;add 0x30 to convert to ASCII LCD_Char movwf templcd swapf templcd, w ;send upper nibble andlw 0x0f ;clear upper 4 bits of W movwf LCD_PORT bsf LCD_PORT, LCD_RS ;RS line to 1 call Pulse_e ;Pulse the E line high movf templcd, w ;send lower nibble andlw 0x0f ;clear upper 4 bits of W movwf LCD_PORT bsf LCD_PORT, LCD_RS ;RS line to 1 call Pulse_e ;Pulse the E line high call LCD_Busy retlw 0x00 LCD_Line1 movlw 0x80 ;move to 1st row, first column call LCD_Cmd retlw 0x00 LCD_Line2 movlw 0xc0 ;move to 2nd row, first column call LCD_Cmd retlw 0x00 LCD_Line1W addlw 0x80 ;move to 1st row, column W call LCD_Cmd retlw 0x00 LCD_Line2W addlw 0xc0 ;move to 2nd row, column W call LCD_Cmd retlw 0x00 LCD_CurOn movlw 0x0d ;Set display on/off and cursor command call LCD_Cmd retlw 0x00 LCD_CurOff movlw 0x0c ;Set display on/off and cursor command call LCD_Cmd retlw 0x00 LCD_Clr movlw 0x01 ;Clear display call LCD_Cmd retlw 0x00 LCD_HEX movwf tmp1 swapf tmp1, w andlw 0x0f call HEX_Table call LCD_Char movf tmp1, w andlw 0x0f call HEX_Table call LCD_Char retlw 0x00 Pulse_e bsf LCD_PORT, LCD_E nop bcf LCD_PORT, LCD_E retlw 0x00 LCD_Busy bsf STATUS, RP0 ;set bank 1 movlw 0x0f ;set Port for input movwf LCD_TRIS bcf STATUS, RP0 ;set bank 0 bcf LCD_PORT, LCD_RS ;set LCD for command mode bsf LCD_PORT, LCD_RW ;setup to read busy flag bsf LCD_PORT, LCD_E swapf LCD_PORT, w ;read upper nibble (busy flag) bcf LCD_PORT, LCD_E movwf templcd2 bsf LCD_PORT, LCD_E ;dummy read of lower nibble bcf LCD_PORT, LCD_E btfsc templcd2, 7 ;check busy flag, high = busy goto LCD_Busy ;if busy check again bcf LCD_PORT, LCD_RW bsf STATUS, RP0 ;set bank 1 movlw 0x00 ;set Port for output movwf LCD_TRIS bcf STATUS, RP0 ;set bank 0 return ;end of LCD routines ;Delay routines Delay255 movlw 0xff ;delay 255 mS goto d0 Delay100 movlw d'100' ;delay 100mS goto d0 Delay50 movlw d'50' ;delay 50mS goto d0 Delay20 movlw d'20' ;delay 20mS goto d0 Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock) d0 movwf count1 d1 movlw 0xC7 movwf counta movlw 0x01 movwf countb Delay_0 decfsz counta, f goto $+2 decfsz countb, f goto Delay_0 decfsz count1 ,f goto d1 retlw 0x00 ;end of Delay routines ;This routine downloaded from http://www.piclist.com Convert: ; Takes number in NumH:NumL ; Returns decimal in ; TenK:Thou:Hund:Tens:Ones swapf NumH, w iorlw B'11110000' movwf Thou addwf Thou,f addlw 0XE2 movwf Hund addlw 0X32 movwf Ones movf NumH,w andlw 0X0F addwf Hund,f addwf Hund,f addwf Ones,f addlw 0XE9 movwf Tens addwf Tens,f addwf Tens,f swapf NumL,w andlw 0X0F addwf Tens,f addwf Ones,f rlf Tens,f rlf Ones,f comf Ones,f rlf Ones,f movf NumL,w andlw 0X0F addwf Ones,f rlf Thou,f movlw 0X07 movwf TenK ; At this point, the original number is ; equal to ; TenK*10000+Thou*1000+Hund*100+Tens*10+Ones ; if those entities are regarded as two's ; complement binary. To be precise, all of ; them are negative except TenK. Now the number ; needs to be normalized, but this can all be ; done with simple byte arithmetic. movlw 0X0A ; Ten Lb1: addwf Ones,f decf Tens,f btfss 3,0 goto Lb1 Lb2: addwf Tens,f decf Hund,f btfss 3,0 goto Lb2 Lb3: addwf Hund,f decf Thou,f btfss 3,0 goto Lb3 Lb4: addwf Thou,f decf TenK,f btfss 3,0 goto Lb4 retlw 0x00 end Tutorial 5.2 - requires one Main Board, one IR Board and Switch Board. This program implements a Sony SIRC IR transmitter, pressing one of the four buttons sends the corresponding code, you can alter the codes as you wish, for this example I chose Volume Up and Down, and Program Up and Down. In order to use this with the LED switching above, I would suggest setting the buttons to transmit '1', '2', '3' and '4', where '4' should have no effect on the LED - the codes are 0x00, 0x01, 0x02, 0x03 respectively (just to confuse us, the number keys start from zero, not from one). ;Tutorial 5.2 - Nigel Goodwin 2002 ;Sony SIRC IR transmitter LIST p=16F628 ;tell assembler what chip we are using include "P16F628.inc" ;include the defaults for the chip __config 0x3D18 ;sets the configuration settings (oscillator type etc.) cblock 0x20 ;start of general purpose registers count1 ;used in delay routine counta ;used in delay routine countb count Delay_Count Bit_Cntr Data_Byte Dev_Byte Rcv_Byte Pulse endc IR_PORT Equ PORTB IR_TRIS Equ TRISB IR_Out Equ 0x01 IR_In Equ 0x02 Ser_Out Equ 0x01 Ser_In Equ 0x02 SW1 Equ 7 ;set constants for the switches SW2 Equ 6 SW3 Equ 5 SW4 Equ 4 TV_ID Equ 0x01 ;TV device ID But1 Equ 0x00 ;numeric button ID's But2 Equ 0x01 But3 Equ 0x02 But4 Equ 0x03 But5 Equ 0x04 But6 Equ 0x05 But7 Equ 0x06 But8 Equ 0x07 But9 Equ 0x08 ProgUp Equ d'16' ProgDn Equ d'17' VolUp Equ d'18' VolDn Equ d'19' org 0x0000 ;org sets the origin, 0x0000 for the 16F628, goto Start ;this is where the program starts running org 0x005 Start movlw 0x07 movwf CMCON ;turn comparators off (make it like a 16F84) clrf IR_PORT ;make PortB outputs low bsf STATUS, RP0 ;select bank 1 movlw b'11111101' ;set PortB all inputs, except RB1 movwf IR_TRIS movlw 0xff movwf PORTA bcf STATUS, RP0 ;select bank 0 Read_Sw btfss PORTA, SW1 call Switch1 btfss PORTA, SW2 call Switch2 btfss PORTA, SW3 call Switch3 btfss PORTA, SW4 call Switch4 call Delay27 goto Read_Sw Switch1 movlw ProgUp call Xmit_RS232 retlw 0x00 Switch2 movlw ProgDn call Xmit_RS232 retlw 0x00 Switch3 movlw VolUp call Xmit_RS232 retlw 0x00 Switch4 movlw VolDn call Xmit_RS232 retlw 0x00 TX_Start movlw d'92' call IR_pulse movlw d'23' call NO_pulse retlw 0x00 TX_One movlw d'46' call IR_pulse movlw d'23' call NO_pulse retlw 0x00 TX_Zero movlw d'23' call IR_pulse movlw d'23' call NO_pulse retlw 0x00 IR_pulse MOVWF count ; Pulses the IR led at 38KHz irloop BSF IR_PORT, IR_Out NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; BCF IR_PORT, IR_Out NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP NOP NOP ; NOP ; DECFSZ count,F GOTO irloop RETLW 0 NO_pulse MOVWF count ; Doesn't pulse the IR led irloop2 BCF IR_PORT, IR_Out NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; BCF IR_PORT, IR_Out NOP ; NOP ; NOP ; NOP ; NOP ; NOP ; NOP NOP NOP ; NOP ; DECFSZ count,F GOTO irloop2 RETLW 0 Xmit_RS232 MOVWF Data_Byte ;move W to Data_Byte MOVLW 0x07 ;set 7 DATA bits out MOVWF Bit_Cntr call TX_Start ;send start bit Ser_Loop RRF Data_Byte , f ;send one bit BTFSC STATUS , C call TX_One BTFSS STATUS , C call TX_Zero DECFSZ Bit_Cntr , f ;test if all done GOTO Ser_Loop ;now send device data movlw D'1' movwf Dev_Byte ;set device to TV MOVLW 0x05 ;set 5 device bits out MOVWF Bit_Cntr Ser_Loop2 RRF Dev_Byte , f ;send one bit BTFSC STATUS , C call TX_One BTFSS STATUS , C call TX_Zero DECFSZ Bit_Cntr , f ;test if all done GOTO Ser_Loop2 retlw 0x00 ;Delay routines Delay255 movlw 0xff ;delay 255 mS goto d0 Delay100 movlw d'100' ;delay 100mS goto d0 Delay50 movlw d'50' ;delay 50mS goto d0 Delay27 movlw d'27' ;delay 27mS goto d0 Delay20 movlw d'20' ;delay 20mS goto d0 Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock) d0 movwf count1 d1 movlw 0xC7 movwf counta movlw 0x01 movwf countb Delay_0 decfsz counta, f goto $+2 decfsz countb, f goto Delay_0 decfsz count1 ,f goto d1 retlw 0x00 ;end of Delay routines end Tutorial 5.3 - requires one Main Board, one IR Board and LED Board. This program implements toggling the 8 LED's on the LED board with the buttons 1 to 8 on a Sony TV remote control, you can easily change the device ID and keys used for the LED's. I've also used a (so far unused) feature of the 16F628, the EEPROM data memory - by using this the program remembers the previous settings when unplugged - when you reconnect the power it restores the last settings by reading them from the internal non-volatile memory. The 16F628 provides 128 bytes of this memory, we only use one here (address 0x00, set in the EEPROM_Addr constant). ;Tutorial 5_3 ;Read SIRC IR and toggle LED display, save settings in EEPROM data memory. ;Nigel Goodwin 2002 LIST p=16F628 ;tell assembler what chip we are using include "P16F628.inc" ;include the defaults for the chip ERRORLEVEL 0, -302 ;suppress bank selection messages __config 0x3D18 ;sets the configuration settings (oscillator type etc.) cblock 0x20 ;start of general purpose registers count ;used in looping routines count1 ;used in delay routine counta ;used in delay routine countb ;used in delay routine LoX Bit_Cntr Cmd_Byte Dev_Byte Flags Flags2 tmp1 ;temporary storage tmp2 tmp3 lastdev lastkey endc LED_PORT Equ PORTB LED_TRIS Equ TRISB IR_PORT Equ PORTA IR_TRIS Equ TRISA IR_In Equ 0x02 ;input assignment for IR data OUT_PORT Equ PORTB LED0 Equ 0x00 LED1 Equ 0x01 LED2 Equ 0x02 LED3 Equ 0x03 LED4 Equ 0x04 LED5 Equ 0x05 LED6 Equ 0x06 LED7 Equ 0x07 EEPROM_Addr Equ 0x00 ;address of EEPROM byte used ErrFlag Equ 0x00 StartFlag Equ 0x01 ;flags used for received bit One Equ 0x02 Zero Equ 0x03 New Equ 0x07 ;flag used to show key released TV_ID Equ 0x01 ;TV device ID But1 Equ 0x00 ;numeric button ID's But2 Equ 0x01 But3 Equ 0x02 But4 Equ 0x03 But5 Equ 0x04 But6 Equ 0x05 But7 Equ 0x06 But8 Equ 0x07 But9 Equ 0x08 org 0x0000 goto Start org 0x0004 retfie Start movlw 0x07 movwf CMCON ;turn comparators off (make it like a 16F84) Initialise clrf count clrf PORTA clrf PORTB clrf Flags clrf Dev_Byte clrf Cmd_Byte SetPorts bsf STATUS, RP0 ;select bank 1 movlw 0x00 ;make all LED pins outputs movwf LED_TRIS movlw b'11111111' ;make all IR port pins inputs movwf IR_TRIS bcf STATUS, RP0 ;select bank 0 call EE_Read ;restore previous settings Main call ReadIR ;read IR signal call ProcKeys ;do something with commands received goto Main ;loop for ever ProcKeys btfss Flags2, New retlw 0x00 ;return if not new keypress movlw TV_ID ;check for TV ID code subwf Dev_Byte, w btfss STATUS , Z retlw 0x00 ;return if not correct code movlw But1 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z goto Key1 ;try next key if not correct code movf LED_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED0 ;and test LED bit for toggling bsf LED_PORT, LED0 ;turn on LED btfsc tmp3, LED0 bcf LED_PORT, LED0 ;turn off LED bcf Flags2, New ;and cancel new flag call EE_Write ;save the settings retlw 0x00 Key1 movlw But2 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z goto Key2 ;try next key if not correct code movf LED_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED1 ;and test LED bit for toggling bsf LED_PORT, LED1 ;turn on LED btfsc tmp3, LED1 bcf LED_PORT, LED1 ;turn off LED bcf Flags2, New ;and cancel new flag call EE_Write ;save the settings retlw 0x00 Key2 movlw But3 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z goto Key3 ;try next key if not correct code movf LED_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED2 ;and test LED bit for toggling bsf LED_PORT, LED2 ;turn on LED btfsc tmp3, LED2 bcf LED_PORT, LED2 ;turn off LED bcf Flags2, New ;and cancel new flag call EE_Write ;save the settings retlw 0x00 Key3 movlw But4 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z goto Key4 ;try next key if not correct code movf LED_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED3 ;and test LED bit for toggling bsf LED_PORT, LED3 ;turn on LED btfsc tmp3, LED3 bcf LED_PORT, LED3 ;turn off LED bcf Flags2, New ;and cancel new flag call EE_Write ;save the settings retlw 0x00 Key4 movlw But5 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z goto Key5 ;try next key if not correct code movf LED_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED4 ;and test LED bit for toggling bsf LED_PORT, LED4 ;turn on LED btfsc tmp3, LED4 bcf LED_PORT, LED4 ;turn off LED bcf Flags2, New ;and cancel new flag call EE_Write ;save the settings retlw 0x00 Key5 movlw But6 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z goto Key6 ;try next key if not correct code movf LED_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED5 ;and test LED bit for toggling bsf LED_PORT, LED5 ;turn on LED btfsc tmp3, LED5 bcf LED_PORT, LED5 ;turn off LED bcf Flags2, New ;and cancel new flag call EE_Write ;save the settings retlw 0x00 Key6 movlw But7 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z goto Key7 ;try next key if not correct code movf LED_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED6 ;and test LED bit for toggling bsf LED_PORT, LED6 ;turn on LED btfsc tmp3, LED6 bcf LED_PORT, LED6 ;turn off LED bcf Flags2, New ;and cancel new flag call EE_Write ;save the settings retlw 0x00 Key7 movlw But8 ;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z retlw 0X00 movf LED_PORT, w ;read PORTB (for LED status) movwf tmp3 ;and store in temp register btfss tmp3, LED7 ;and test LED bit for toggling bsf LED_PORT, LED7 ;turn on LED btfsc tmp3, LED7 bcf LED_PORT, LED7 ;turn off LED bcf Flags2, New ;and cancel new flag call EE_Write ;save the settings retlw 0x00 EE_Read bsf STATUS, RP0 ; Bank 1 movlw EEPROM_Addr movwf EEADR ; Address to read bsf EECON1, RD ; EE Read movf EEDATA, W ; W = EEDATA bcf STATUS, RP0 ; Bank 0 movwf LED_PORT ; restore previous value retlw 0x00 EE_Write movf LED_PORT, w ; read current value bsf STATUS, RP0 ; Bank 1 bsf EECON1, WREN ; Enable write movwf EEDATA ; set EEPROM data movlw EEPROM_Addr movwf EEADR ; set EEPROM address movlw 0x55 movwf EECON2 ; Write 55h movlw 0xAA movwf EECON2 ; Write AAh bsf EECON1, WR ; Set WR bit ; begin write bcf STATUS, RP0 ; Bank 0 btfss PIR1, EEIF ; wait for write to complete. goto $-1 bcf PIR1, EEIF ; and clear the 'write complete' flag bsf STATUS, RP0 ; Bank 1 bcf EECON1, WREN ; Disable write bcf STATUS, RP0 ; Bank 0 retlw 0x00 ;IR routines ReadIR call Read_Pulse btfss Flags, StartFlag goto ReadIR ;wait for start pulse (2.4mS) Get_Data movlw 0x07 ;set up to read 7 bits movwf Bit_Cntr clrf Cmd_Byte Next_RcvBit2 call Read_Pulse btfsc Flags, StartFlag ;abort if another Start bit goto ReadIR btfsc Flags, ErrFlag ;abort if error goto ReadIR bcf STATUS , C btfss Flags, Zero bsf STATUS , C rrf Cmd_Byte , f decfsz Bit_Cntr , f goto Next_RcvBit2 rrf Cmd_Byte , f ;correct bit alignment for 7 bits Get_Cmd movlw 0x05 ;set up to read 5 bits movwf Bit_Cntr clrf Dev_Byte Next_RcvBit call Read_Pulse btfsc Flags, StartFlag ;abort if another Start bit goto ReadIR btfsc Flags, ErrFlag ;abort if error goto ReadIR bcf STATUS , C btfss Flags, Zero bsf STATUS , C rrf Dev_Byte , f decfsz Bit_Cntr , f goto Next_RcvBit rrf Dev_Byte , f ;correct bit alignment for 5 bits rrf Dev_Byte , f rrf Dev_Byte , f retlw 0x00 ;end of ReadIR ;read pulse width, return flag for StartFlag, One, Zero, or ErrFlag ;output from IR receiver is normally high, and goes low when signal received Read_Pulse clrf LoX btfss IR_PORT, IR_In ;wait until high goto $-1 clrf tmp1 movlw 0xC0 ;delay to decide new keypress movwf tmp2 ;for keys that need to toggle Still_High btfss IR_PORT, IR_In ;and wait until goes low goto Next incfsz tmp1,f goto Still_High incfsz tmp2,f goto Still_High bsf Flags2, New ;set New flag if no button pressed goto Still_High Next nop nop nop nop nop ;waste time to scale pulse nop ;width to 8 bits nop nop nop nop nop nop incf LoX, f btfss IR_PORT, IR_In goto Next ;loop until input high again ; test if Zero, One, or Start (or error) Chk_Pulse clrf Flags TryError movf LoX, w ; check if pulse too small addlw d'255' - d'20' ; if LoX <= 20 btfsc STATUS , C goto TryZero bsf Flags, ErrFlag ; Error found, set flag retlw 0x00 TryZero movf LoX, w ; check if zero addlw d'255' - d'60' ; if LoX <= 60 btfsc STATUS , C goto TryOne bsf Flags, Zero ; Zero found, set flag retlw 0x00 TryOne movf LoX, w ; check if one addlw d'255' - d'112' ; if LoX <= 112 btfsc STATUS , C goto TryStart bsf Flags, One ; One found, set flag retlw 0x00 TryStart movf LoX, w ; check if start addlw d'255' - d'180' ; if LoX <= 180 btfsc STATUS , C goto NoMatch bsf Flags, StartFlag ; Start pulse found retlw 0x00 NoMatch ; pulse too long bsf Flags, ErrFlag ; Error found, set flag retlw 0x00 ;end of pulse measuring routines ;Delay routines Delay255 movlw 0xff ;delay 255 mS goto d0 Delay100 movlw d'100' ;delay 100mS goto d0 Delay50 movlw d'50' ;delay 50mS goto d0 Delay20 movlw d'20' ;delay 20mS goto d0 Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock) d0 movwf count1 d1 movlw 0xC7 movwf counta movlw 0x01 movwf countb Delay_0 decfsz counta, f goto $+2 decfsz countb, f goto Delay_0 decfsz count1 ,f goto d1 retlw 0x00 ;end of Delay routines end The EEPROM data is accessed by two new routines, EE_Read and EE_Write, the EE_Read routine is called as the program powers up, before we enter the main loop, and the EE_Write routine is called after every LED change. The EE_Read routine is very straightforward, we simply set the address we wish to read in the EEADR register, set the RD flag in the EECON1 register, and then read the data from the EEDATA register. Writing is somewhat more complicated, for a couple of reasons:
The extra work involved makes the EE_Write routine a lot longer than the EE_Read routine, it also doesn't help that we need to access registers in different banks, so we do a fair bit of bank switching. |